﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

/// <summary>
/// 一个虚拟摇杆的实现，用于在Unity中提供触摸或鼠标控制。
/// </summary>
public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
    // 属性：获取水平方向的输入值，根据snapX决定是否进行数值归一化
    public float Horizontal { get { return (snapX) ? SnapFloat(input.x, AxisOptions.Horizontal) : input.x; } }
    
    // 属性：获取垂直方向的输入值，根据snapY决定是否进行数值归一化
    public float Vertical { get { return (snapY) ? SnapFloat(input.y, AxisOptions.Vertical) : input.y; } }
    
    // 属性：获取当前的方向向量
    public Vector2 Direction { get { return new Vector2(Horizontal, Vertical); } }

    // 属性：设置和获取摇杆柄的最大移动范围，确保为正值
    public float HandleRange
    {
        get { return handleRange; }
        set { handleRange = Mathf.Abs(value); }
    }

    // 属性：设置和获取死区大小，确保为正值
    public float DeadZone
    {
        get { return deadZone; }
        set { deadZone = Mathf.Abs(value); }
    }

    // 属性：设置和获取轴选项（水平、垂直或两者），注意这里存在属性名与字段名相同的问题
    public AxisOptions AxisOptions 
    { 
        get { return axisOptions; } 
        set { axisOptions = value; } 
    }

    // 属性：设置和获取是否对X轴进行归一化处理
    public bool SnapX { get { return snapX; } set { snapX = value; } }

    // 属性：设置和获取是否对Y轴进行归一化处理
    public bool SnapY { get { return snapY; } set { snapY = value; } }

    // 序列化的私有字段：摇杆柄的默认范围
    [SerializeField] private float handleRange = 1;

    // 序列化的私有字段：死区大小
    [SerializeField] private float deadZone = 0;

    // 序列化的私有字段：轴选项，默认为Both（同时支持水平和垂直）
    [SerializeField] private AxisOptions axisOptions = AxisOptions.Both;

    // 序列化的私有字段：是否对X轴进行归一化处理
    [SerializeField] private bool snapX = false;

    // 序列化的私有字段：是否对Y轴进行归一化处理
    [SerializeField] private bool snapY = false;

    // 序列化的私有字段：背景RectTransform引用
    [SerializeField] protected RectTransform background = null;

    // 序列化的私有字段：摇杆柄RectTransform引用
    [SerializeField] private RectTransform handle = null;

    // 私有字段：基础RectTransform引用
    private RectTransform baseRect = null;

    // 私有字段：Canvas组件引用
    private Canvas canvas;

    // 私有字段：Camera组件引用
    private Camera cam;

    // 私有字段：当前输入值
    private Vector2 input = Vector2.zero;

    /// <summary>
    /// 初始化方法，在场景开始时调用。
    /// 设置摇杆的基础属性，并检查是否位于Canvas内。
    /// </summary>
    protected virtual void Start()
    {
        HandleRange = handleRange;
        DeadZone = deadZone;
        baseRect = GetComponent<RectTransform>();
        canvas = GetComponentInParent<Canvas>();
        if (canvas == null)
            Debug.LogError("The Joystick is not placed inside a canvas");

        Vector2 center = new Vector2(0.5f, 0.5f);
        background.pivot = center;
        handle.anchorMin = center;
        handle.anchorMax = center;
        handle.pivot = center;
        handle.anchoredPosition = Vector2.zero;
    }

    /// <summary>
    /// 当用户按下摇杆时调用。
    /// </summary>
    /// <param name="eventData">事件数据</param>
    public virtual void OnPointerDown(PointerEventData eventData)
    {
        OnDrag(eventData);
    }

    /// <summary>
    /// 当用户拖动摇杆时调用。
    /// </summary>
    /// <param name="eventData">事件数据</param>
    public void OnDrag(PointerEventData eventData)
    {
        cam = null;
        if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
            cam = canvas.worldCamera;

        Vector2 position = RectTransformUtility.WorldToScreenPoint(cam, background.position);
        Vector2 radius = background.sizeDelta / 2;
        input = (eventData.position - position) / (radius * canvas.scaleFactor);
        FormatInput();
        HandleInput(input.magnitude, input.normalized, radius, cam);
        handle.anchoredPosition = input * radius * handleRange;
    }

    /// <summary>
    /// 处理输入逻辑。
    /// </summary>
    /// <param name="magnitude">输入向量的长度</param>
    /// <param name="normalised">归一化后的输入向量</param>
    /// <param name="radius">半径</param>
    /// <param name="cam">相机引用</param>
    protected virtual void HandleInput(float magnitude, Vector2 normalised, Vector2 radius, Camera cam)
    {
        if (magnitude > deadZone)
        {
            if (magnitude > 1)
                input = normalised;
        }
        else
            input = Vector2.zero;
    }

    /// <summary>
    /// 格式化输入，根据轴选项调整输入值。
    /// </summary>
    private void FormatInput()
    {
        if (axisOptions == AxisOptions.Horizontal)
            input = new Vector2(input.x, 0f);
        else if (axisOptions == AxisOptions.Vertical)
            input = new Vector2(0f, input.y);
    }

    /// <summary>
    /// 对浮点数进行归一化处理。
    /// </summary>
    /// <param name="value">需要处理的值</param>
    /// <param name="snapAxis">轴选项</param>
    /// <returns>归一化后的值</returns>
    private float SnapFloat(float value, AxisOptions snapAxis)
    {
        if (value == 0)
            return value;

        if (axisOptions == AxisOptions.Both)
        {
            float angle = Vector2.Angle(input, Vector2.up);
            if (snapAxis == AxisOptions.Horizontal)
            {
                if (angle < 22.5f || angle > 157.5f)
                    return 0;
                else
                    return (value > 0) ? 1 : -1;
            }
            else if (snapAxis == AxisOptions.Vertical)
            {
                if (angle > 67.5f && angle < 112.5f)
                    return 0;
                else
                    return (value > 0) ? 1 : -1;
            }
            return value;
        }
        else
        {
            if (value > 0)
                return 1;
            if (value < 0)
                return -1;
        }
        return 0;
    }

    /// <summary>
    /// 当用户释放摇杆时调用。
    /// </summary>
    /// <param name="eventData">事件数据</param>
    public virtual void OnPointerUp(PointerEventData eventData)
    {
        input = Vector2.zero;
        handle.anchoredPosition = Vector2.zero;
    }

    /// <summary>
    /// 将屏幕坐标转换为锚点位置。
    /// </summary>
    /// <param name="screenPosition">屏幕坐标</param>
    /// <returns>锚点位置</returns>
    protected Vector2 ScreenPointToAnchoredPosition(Vector2 screenPosition)
    {
        Vector2 localPoint = Vector2.zero;
        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(baseRect, screenPosition, cam, out localPoint))
        {
            Vector2 pivotOffset = baseRect.pivot * baseRect.sizeDelta;
            return localPoint - (background.anchorMax * baseRect.sizeDelta) + pivotOffset;
        }
        return Vector2.zero;
    }
}

/// <summary>
/// 枚举：定义了摇杆支持的轴选项。
/// </summary>
public enum AxisOptions { Both, Horizontal, Vertical }